# coding=utf-8

"""
检查目录是否有更新文件，存在更新文件复制到脚本所在目录中
"""
import os,time,sys,inspect,getopt,hashlib,ConfigParser
import stat,string,ctypes

#获取脚本所在的路径
def current_path():   
    path=os.path.realpath(sys.path[0])  
    if os.path.isfile(path):  
        path=os.path.dirname(path)  
        return os.path.abspath(path)  
    else:  
        caller_file=inspect.stack()[1][1]  
        return os.path.abspath(os.path.dirname(caller_file))

#日期转时间戳        
def datetime_timestamp(dt):
  s = time.mktime(time.strptime(dt, '%Y-%m-%d %H:%M:%S'))
  return int(s)

#检查配置文件是否正确 
def checkini(config_file):
  try:
    ck_fp=open(config_file,'r')
    ck_fp.close()
  except:
    print u'配置文件config.ini不存在'
    sys.exit()
    
  ck_cf = ConfigParser.ConfigParser()
  try:
    ck_cf.read(config_file)
    versionnum=ck_cf.getint('version','versionnum')
    if versionnum<1:
      print u'参数[version]-versionnum不正确'
      sys.exit()
    versionnames=[]
    for versionindex in xrange(1,versionnum+1):
      versionnames.append(ck_cf.get('version',str(versionindex)))
    
    sections=ck_cf.sections()
    for versionname in versionnames:
      if 'code_'+versionname not in sections:
         print u'配置文件中缺少[code_'+versionname+u']段'
         sys.exit()
      code_folder = ck_cf.get('code_'+versionname,'code_folder') 

      if not os.path.exists(code_folder):
         print '[code_'+versionname+u'][code_folder]的目录'+code_folder+u'不存在'
         sys.exit()
         
      exclude_path = ck_cf.get('code_'+versionname,'exclude_path').replace('\\','/')
      if exclude_path!='':
        paths=string.split(exclude_path,',')
        for path in paths:
          if not os.path.exists(code_folder+path):
            print u'版本'+versionname+u':[code_'+versionname+u'][exclude_path]中的目录'+path+u'不存在'
            sys.exit()

      exclude_file = ck_cf.get('code_'+versionname,'exclude_file').replace('\\','/')
      if exclude_file!='':
        files=string.split(exclude_file,',')
        for exfile in files:
          if not os.path.exists(code_folder+exfile):
            print u'版本'+versionname+u':[code_'+versionname+u'][exclude_file]中的文件'+exfile+u'不存在'
            sys.exit()
                      
      if 'version_'+versionname not in sections:
        print u'配置文件中缺少[version_'+versionname+u']段'
        sys.exit() 
      version_folder=ck_cf.get('version_'+versionname,'version_folder')
      
      if not os.path.exists(version_folder):
        print '[version_'+versionname+u'][version_folder]的目录'+version_folder+u'不存在'
        sys.exit()

      revision=ck_cf.getint('version_'+versionname,'revision')
      if revision<0:
        print '[version_'+versionname+u']revision参数不能小于零'

  except Exception,e:
    print u'检查配置文件失败'
    print e
    sys.exit()
  
def  usage():
  print u"-s 版本名"
  print u"-l 列出版本"
  print u"-h help"
  
class Check_version:
  def __init__(self,cf,versionname):
    self.versionname=versionname
    self.cf=cf
    self.code_folder = self.cf.get('code_'+self.versionname,'code_folder')
    self.exclude_path = self.cf.get('code_'+self.versionname,'exclude_path').replace('\\','/')
    self.exclude_file = self.cf.get('code_'+self.versionname,'exclude_file').replace('\\','/')
    self.version_folder= self.cf.get('version_'+self.versionname,'version_folder') 
    self.revision = self.cf.getint('version_'+self.versionname,'revision')
    self.version_path = os.path.join(self.version_folder,self.versionname).replace('\\','/')+'/'  #版本文件路径
    self.revision_path = os.path.join(self.version_folder,str(self.revision+1)).replace('\\','/')+'/' #修订版本的路径，存放本次更新的文件
    self.errorinfo='success'  #错误信息
    self.codefiles=[]  #代码文件列表
    self.versionfiles=[] #版本文件列表
    self.opfiles=[] #操作的文件列表  内部为字典数据 状态st:U 更新 A 新增 D 删除  O 其他 操作op:C 复制 D 删除
    try:
      os.chdir(self.code_folder)
    except:
      self.displaymsg(self.code_folder+u'目录不存在','red')
      self.writeLogs(self.code_folder+u'目录不存在')
      self.errorinfo='error'
      
  def __del__(self):
    pass

  def writeLogs(self,msg):
    global logfile
    msg=time.strftime('%Y-%m-%d %H:%M:%S')+'  '+u'[版本'+self.versionname+'] '+unicode(msg)+'\n'
    f=open(logfile,'a+')
    f.write(msg.encode('utf8'))
    f.close()
    
  def displaymsg(self,msg,color='white'):
    if os.name=='nt':
       ctypes.windll.Kernel32.GetStdHandle.restype = ctypes.c_ulong
       h = ctypes.windll.Kernel32.GetStdHandle(ctypes.c_ulong(0xfffffff5))
       if color=='white':
          ctypes.windll.Kernel32.SetConsoleTextAttribute(h, 15)
       elif color=='red':
          ctypes.windll.Kernel32.SetConsoleTextAttribute(h, 12)
       elif color=='green':
          ctypes.windll.Kernel32.SetConsoleTextAttribute(h, 10)
       elif color=='blue':
          ctypes.windll.Kernel32.SetConsoleTextAttribute(h, 9)
       elif color=='yellow':
          ctypes.windll.Kernel32.SetConsoleTextAttribute(h, 14)
    print u'版本'+self.versionname+' '+unicode(msg)
    if os.name=='nt':  #恢复为白色
       ctypes.windll.Kernel32.SetConsoleTextAttribute(h, 15)
       

  def walk_path(self,top):
    flist=[]
    for root, dirs, files in os.walk(top, topdown=False):
        for name in files:
            f=os.path.join(root, name).replace('\\','/')[len(top):]
            flist.append(f.decode('gbk')) #中文目录
    return flist        

  def getFilesInCode(self):
    if self.errorinfo!='success':
      self.displaymsg(u'版本检测出错','red')
      return   
    self.codefiles.extend(self.walk_path(self.code_folder))

  def getFilesInVersion(self):
    if self.errorinfo!='success':
      self.displaymsg(u'版本检测出错','red')
      return
    if os.path.exists(self.version_path):
      self.versionfiles.extend(self.walk_path(self.version_path))

  #计算文件md5
  def CalMD5(self,filename):
    hashobj = hashlib.md5()
    with open(filename, 'rb') as f:       
      hashobj.update(f.read())
    md5_str = hashobj.hexdigest()
    return md5_str

  #更新版本号
  def setRevision(self):
    global config_file
    self.cf.set("version_"+self.versionname, "revision", str(self.revision+1))
    self.cf.write(open(config_file, "w"))
  
  #修订日志，记录修订版本的文件更新情况
  def writeRevisionLog(self):
    revisionlog=os.path.join(self.revision_path,'revision.log')
    try:
      f=open(revisionlog,'a+')
      msg=time.strftime('%Y-%m-%d %H:%M:%S')
      f.write(msg.encode('utf8')+'\n')
      for action in self.opfiles:
        if action['st']=='A':
          msg=u'【新增文件】'+action['filename']
          f.write(msg.encode('utf8')+'\n')
        elif action['st']=='U':
          msg=u'【更新文件】'+action['filename']
          f.write(msg.encode('utf8')+'\n')
        elif action['st']=='D':
          msg=u'【删除文件】'+action['filename']
          f.write(msg.encode('utf8')+'\n')
    except Exception,e:
      print e
      return False
    finally:
      f.close()
    return True 
  
  #版本日志
  def writeVersionLog(self):
    versionlog=os.path.join(self.version_folder,'version.log')

    try:
      f=open(versionlog,'a+')
      for action in self.opfiles:
        if action['st']!='O':  #不是其他操作
          msg=action['filename']+u':'+unicode(str(self.revision+1))
          f.write(msg.encode('utf8')+'\n')
    except Exception,e:
      print e
      return False
    finally:
      f.close()
    return True

  
  #检查版本更新
  def check_all_file(self):
    global script_path
    if self.errorinfo!='success':
      self.displaymsg(u'版本检测出错','red')
      return
    copyNum=0 #复制文件数量 
    successNum=0 #成功数量
    failNum=0 #失败数量
    
    self.writeLogs(u'开始检查代码版本')
    #查找新增和更新文件
    for line in self.codefiles:  #检查代码文件中是否有更新或新增的文件，并过滤排除的目录中的文件
        filename=line.strip() 
        
        #排除目录
        if self.exclude_path!='':
          exclude_paths= string.split(self.exclude_path,',')
          findpath=False
          for expath in exclude_paths:
            if expath+'/'==filename[0:len(expath)+1]:  #加斜杠防止不完全匹配
              #self.displaymsg(u'排除目录'+expath+u'中的文件'+filename)
              self.writeLogs(u'排除目录'+expath+u'中的文件'+filename)
              findpath=True
              break
          if findpath:
            continue

        #排除文件
        if self.exclude_file!='':
          exclude_files= string.split(self.exclude_file,',')
          findfile=False
          for exfile in exclude_files:
            if exfile==filename:
              #self.displaymsg(u'排除文件'+exfile)
              self.writeLogs(u'排除文件'+exfile)
              findfile=True
              break
          if findfile:
            continue
                                      
        codefullname=os.path.join(self.code_folder,filename).replace('\\','/')
        versionfullname=os.path.join(self.version_path,filename).replace('\\','/')
        if os.path.isfile(codefullname):
          codeMD5=self.CalMD5(codefullname)
          if os.path.exists(versionfullname):
            versionMD5=self.CalMD5(versionfullname)    
            if codeMD5 != versionMD5:#文件MD5不一样
              self.opfiles.append({'st':'U','op':'C','source':versionfullname,'dest':os.path.join(self.revision_path,filename).replace('\\','/'),'filename':filename}) #版本文件复制到修订目录      
              self.opfiles.append({'st':'O','op':'C','source':codefullname,'dest':versionfullname,'filename':filename}) #代码文件复制到版本文件        
          else: #版本中不存在此文件
            self.opfiles.append({'st':'A','op':'C','source':codefullname,'dest':os.path.join(self.revision_path,filename).replace('\\','/'),'filename':filename}) #代码文件复制到修订目录  
            self.opfiles.append({'st':'O','op':'C','source':codefullname,'dest':versionfullname,'filename':filename})  #代码文件复制到版本文件
    
    #查找删除文件                   
    if len(self.versionfiles)!=0:
      for line in self.versionfiles:  #检查代码文件中是否有删除的文件
        filename=line.strip()
      
        codefullname=os.path.join(self.code_folder,filename).replace('\\','/')
        versionfullname=os.path.join(self.version_path,filename).replace('\\','/') 
        if os.path.isfile(versionfullname):
          if not os.path.exists(codefullname):
            self.opfiles.append({'st':'D','op':'C','source':versionfullname,'dest':os.path.join(self.revision_path,filename).replace('\\','/'),'filename':filename}) #版本文件复制到修订目录
            self.opfiles.append({'st':'O','op':'D','source':versionfullname,'dest':'','filename':filename}) #删除版本文件

  
  def copy_file(self,sourcefile,destfile,failNum,successNum):
    file_handler = open(sourcefile,'rb')
    try:
        open(destfile, "wb").write(file_handler.read()) 
    except Exception,e:
        try:
            os.makedirs(os.path.dirname(destfile))                    
            open(destfile, "wb").write(file_handler.read()) 
        except Exception,e:
            self.displaymsg(u'复制文件'+sourcefile+u'失败 '+unicode(e),'red')
            self.writeLogs(u'复制文件'+sourcefile+u'失败 '+unicode(e))
            failNum=failNum+1
            return failNum,successNum
    finally:        
        file_handler.close()
    self.writeLogs(u'复制文件'+sourcefile+u'成功')
    successNum=successNum+1
    return failNum,successNum

  def delete_file(self,filename,failNum,successNum):
    try:
      os.remove(filename)
    except:
      self.displaymsg(u'删除文件'+filename+u'失败 '+unicode(e),'red')
      self.writeLogs(u'删除文件'+filename+u'失败 '+unicode(e))
      failNum=failNum+1
      return failNum,successNum
    self.writeLogs(u'删除文件'+filename+u'成功')
    successNum=successNum+1
    return failNum,successNum

  def version_list(self):
     for action in self.opfiles:
        if action['st']=='A':
          self.displaymsg(u'【新增文件】'+action['filename'],'blue')
          self.writeLogs(u'【新增文件】'+action['filename'])
        elif action['st']=='U':
          self.displaymsg(u'【更新文件】'+action['filename'],'blue')
          self.writeLogs(u'【更新文件】'+action['filename'])
        elif action['st']=='D':
          self.displaymsg(u'【删除文件】'+action['filename'],'blue')
          self.writeLogs(u'【删除文件】'+action['filename'])

  def commit_version(self):
     successNum=0
     failNum=0
     for action in self.opfiles:
        if action['op']=='C':
          failNum,successNum=self.copy_file(action['source'],action['dest'],failNum,successNum)
        elif action['op']=='D':
          failNum,successNum=self.delete_file(action['source'],failNum,successNum)
     if failNum==0:
        if not self.writeRevisionLog():
          self.displaymsg(u'更新修订日志失败','red')
          self.writeLogs(u'更新修订日志失败')
          return
        if not self.writeVersionLog():
          self.displaymsg(u'更新版本日志失败','red')
          self.writeLogs(u'更新版本日志失败')
          return         
        self.setRevision()  #更新修订版本号
        self.displaymsg(u'文件提交成功','green')
        self.writeLogs(u'文件提交成功')    
     else:
        self.displaymsg(u'文件提交失败，请查看日志','red')
        self.writeLogs(u'文件提交失败，请查看日志')            
    
  def check_version(self):
    self.getFilesInCode()
    self.getFilesInVersion()
    self.check_all_file()
    if len(self.opfiles)==0:
      self.displaymsg(u'代码中没有更新的文件')
      self.writeLogs(u'代码中没有更新的文件')
    else:
      self.displaymsg(u'文件更新列表：')
      self.version_list()
      response=raw_input('是否提交文件？[y/n]'.decode('utf-8').encode('gbk')+'\n')
      if response.lower()!='y':
        sys.exit()
      else:
        self.displaymsg(u'文件提交中')
        self.commit_version()
    

if __name__=='__main__':    
  if len(sys.argv)<=1:
    usage()
    sys.exit()
  
  version=''
  listversion=False  #是否列版本 
  
  try:
    opts, args = getopt.getopt(sys.argv[1:], "hls:")
  except:
    usage()
    sys.exit()
         
  for op, value in opts:
      if op == "-h":
         usage()
         sys.exit()
      elif op == "-l":
         listversion=True
      elif op== "-s":
         version=value
      else:
         usage()
         sys.exit()
  
  script_path=current_path()
  riqi = time.strftime('%Y%m%d%H%M%S', time.localtime())
  log_path = os.path.join(script_path, 'log')
  if not os.path.exists(log_path):
    os.makedirs(log_path)
  logfile=os.path.join(script_path,'log',riqi+'_'+version+'.log')
  
  config_file= os.path.join(script_path,'config.ini') 
  
  checkini(config_file)

  cf=ConfigParser.ConfigParser()
  cf.read(config_file)
  versionnum=cf.getint('version','versionnum')
  versionnames=[]
  for versionindex in xrange(1,versionnum+1):
    versionnames.append(cf.get('version',str(versionindex)))
  
  #列出版本
  if listversion:
    for versionname in versionnames:
      print versionname
    sys.exit()
    
  if version=='':
    usage()
    sys.exit()
  
  if version not in versionnames:
    print u'版本'+version+u'不存在'
    sys.exit()
  
  print u'检查版本'+version
  versioncheck=Check_version(cf,version)
  versioncheck.check_version() 




